﻿//-------------------------------------------------------------------------------
// <copyright file="LinqToSQLWorkspace.cs" company="bbv Software Services AG">
//   Copyright (c) 2008 bbv Software Services AG
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// </copyright>
//------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace bbv.DomainDrivenDesign.Workspaces
{
    using System;
    using System.Data;
    using System.Data.Linq;
    using System.Reflection;
    using Contexts;
    using Interfaces;
    using log4net;
    using QueryableContexts;

    /// <summary>
    /// Implements a unit of work for the LinqToSql object/relational mapping framework.
    /// </summary>
    public class LinqToSqlWorkspace : IWorkspace
    {
        #region logger

        /// <summary>
        /// Type specific logger 
        /// </summary>
        private static readonly ILog Log =
            LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        #endregion

        /// <summary>
        /// Holds a reference to the <see cref="DataContext"/>.
        /// </summary>
        private readonly DataContext context;

        //        internal DataLoadOptions m_dataLoadOptions = new DataLoadOptions();
        internal Dictionary<Type, Dictionary<Expression, IEnumerable<ParameterExpression>>> m_loadOptions;


        /// <summary>
        /// Initializes a new instance of the <see cref="LinqToSqlWorkspace"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        public LinqToSqlWorkspace(DataContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            this.context = context;
            m_loadOptions = new Dictionary<Type, Dictionary<Expression, IEnumerable<ParameterExpression>>>();
        }

        /// <summary>
        /// Gets the underlying connection of the unit of work.
        /// </summary>
        /// <value>The connection as a <see cref="IDbConnection"/>.</value>
        public IDbConnection Connection
        {
            get { return context.Connection; }
        }

        /// <summary>
        /// Persists all pending updates to the data source.
        /// </summary>
        /// <exception cref="OptimisticOfflineLockException"/>
        public void SubmitChanges()
        {
            try
            {
                context.SubmitChanges();
            }
            catch (ChangeConflictException e)
            {
                Log.Debug("Optimistic concurrency error.");
                Log.Debug(e.Message);

                throw new OptimisticOfflineLockException(e.Message, e);
            }
        }

        /// <summary>
        /// Accepts all changes made to objects 
        /// in the <see cref="IWorkspace"/> context.
        /// </summary>
        public void AcceptAllChanges()
        {
            throw new NotSupportedException();
        }

        public T GetById<T, TID>(TID id) where T : class
        {
            var table = CreateQuery<T>();
            var mapping = context.Mapping.GetTable(typeof(T));

            //if(id is IMultiPrimaryKey<T>)
            //{
            //    return table.SingleOrDefault((id as IMultiPrimaryKey<T>).IdSpecification);
            //}

            var pkfield = mapping.RowType.DataMembers.SingleOrDefault(f => f.IsPrimaryKey);

            if (pkfield != null)
            {
                if (typeof(TID) != pkfield.Type)
                {
                    throw new ArgumentException("The type of the id field does not match with the one in the mapping.");
                }
                var param = Expression.Parameter(typeof(T), "e");
                var predicate = Expression.Lambda<Func<T, bool>>(Expression.Equal(Expression.Property(param, pkfield.Name),
                                                                Expression.Constant(id)), param);
                var item = table.SingleOrDefault(predicate);
                return item;
            }
            return null;
        }

        /// <summary>
        /// Return the persistent instance of the given named entity with the given identifier,
        /// or null if there is no such persistent instance. (If the instance, or a proxy for the
        /// instance, is already associated with the session, return that instance or proxy.)
        /// </summary>
        /// <typeparam name="T">the type of the entity</typeparam>
        /// <param name="entityWithKey">an entity with a key</param>
        /// <returns>a persistent instance or null</returns>
        public T Get<T>(T entityWithKey) where T : class
        {
            return this.context.GetObjectByKey(entityWithKey);
        }

        /// <summary>
        /// Inserts an entity in a pending insert state to the <see cref="IWorkspace"/> context.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">A transient instance of a persistent class.</param>
        public void Add<T>(T entity) where T : class
        {
            context.GetTable<T>().InsertOnSubmit(entity);
        }

        public T Create<T>(T entity) where T : class
        {
            Add(entity);
            SubmitChanges();
            return entity;
        }

        /// <summary>
        /// Remove an entity from this table into a pending delete state.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">The instance to be removed.</param>
        public void Delete<T>(T entity) where T : class
        {
            context.GetTable<T>().DeleteOnSubmit(entity);
        }

        /// <summary>
        /// Update the persistent instance with the identifier of the given transient instance.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">The detached object that has property updates
        /// to apply to the original object.</param>
        /// <remarks>
        /// If there is a persistent instance with the same identifier, an exception is thrown. If
        /// the given transient instance has a <see langword="null"/> identifier, an exception will be thrown.
        /// </remarks>
        public void Update<T>(T entity) where T : class
        {
            SubmitChanges();
        }

        /// <summary>
        /// Cleans all cached objects within the <see cref="IWorkspace"/>.
        /// </summary>
        public void Clean()
        {
            context.ClearCache();
        }

        /// <summary>
        /// Updates an object in the object context with data from the persisted store.
        /// Overrides all the current values with the values from the database.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">The entity to be refreshed.</param>
        public void Refresh<T>(T entity) where T : class
        {
            context.Refresh(RefreshMode.OverwriteCurrentValues, entity);
        }

        /// <summary>
        /// Attach an object or object graph to the object context if the object
        /// has an entity key.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entityWithKey">The entity.</param>
        public void Attach<T>(T entityWithKey) where T : class
        {
            context.GetTable<T>().Attach(entityWithKey);
        }

        /// <summary>
        /// Removes the specified object from the <see cref="IWorkspace"/> context.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">The entity.</param>
        public void Detach<T>(T entity) where T : class
        {
            context.Detach(entity);
        }

        /// <summary>
        /// Creates an <see cref="T:bbv.DomainDrivenDesign.Interfaces.IQueryableContext`1"/> in the current object context
        /// </summary>
        /// <typeparam name="T">The type of the IQueryableContext.</typeparam>
        /// <returns>An <see cref="T:bbv.DomainDrivenDesign.Interfaces.IQueryableContext`1"/> of the specified type.</returns>
        public IQueryableContext<T> CreateQuery<T>() where T : class
        {
            return new LinqToSqlQueryableContext<T>(context, this);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, 
        /// releasing, or resetting unmanaged resources.
        /// </summary>
        public virtual void Dispose()
        {
            context.Dispose();
        }
    }
}
